From a4272ef2b71bad8b32654584b233540205c326db Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 21 Jul 2014 22:19:31 -0700 Subject: [PATCH] Implement cargo-new This command is used to create a new cargo repository at a destination that previously does not exist. A separate command, cargo-init, will be implemented to initialize an already-existing repository. cc #21 --- Makefile | 3 +- src/bin/cargo-new.rs | 52 ++++++++++++++ src/cargo/ops/cargo_new.rs | 94 ++++++++++++++++++++++++ src/cargo/ops/mod.rs | 2 + src/cargo/util/config.rs | 2 + tests/test_cargo_compile_git_deps.rs | 2 +- tests/test_cargo_new.rs | 103 +++++++++++++++++++++++++++ tests/tests.rs | 1 + 8 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 src/bin/cargo-new.rs create mode 100644 src/cargo/ops/cargo_new.rs create mode 100644 tests/test_cargo_new.rs diff --git a/Makefile b/Makefile index 13d5d6f3b..548b61d85 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,8 @@ BINS = cargo \ cargo-git-checkout \ cargo-test \ cargo-run \ - cargo-version + cargo-version \ + cargo-new SRC = $(shell find src -name '*.rs' -not -path 'src/bin*') diff --git a/src/bin/cargo-new.rs b/src/bin/cargo-new.rs new file mode 100644 index 000000000..efafc54b7 --- /dev/null +++ b/src/bin/cargo-new.rs @@ -0,0 +1,52 @@ +#![feature(phase)] + +extern crate cargo; + +#[phase(plugin, link)] +extern crate hammer; + +#[phase(plugin, link)] +extern crate log; + +extern crate serialize; + +use std::os; +use cargo::ops; +use cargo::core::MultiShell; +use cargo::util::{CliResult, CliError}; + +#[deriving(PartialEq,Clone,Decodable,Encodable)] +pub struct Options { + git: bool, + bin: bool, + rest: Vec, +} + +hammer_config!(Options "Create a new cargo project") + +fn main() { + cargo::execute_main_without_stdin(execute); +} + +fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { + debug!("executing; cmd=cargo-new; args={}", os::args()); + + let Options { git, mut rest, bin } = options; + + let path = match rest.remove(0) { + Some(path) => path, + None => return Err(CliError::new("must have a path as an argument", 1)) + }; + + let opts = ops::NewOptions { + git: git, + path: path.as_slice(), + bin: bin, + }; + + ops::new(opts, shell).map(|_| None).map_err(|err| { + CliError::from_boxed(err, 101) + }) +} + + diff --git a/src/cargo/ops/cargo_new.rs b/src/cargo/ops/cargo_new.rs new file mode 100644 index 000000000..3039a5e3c --- /dev/null +++ b/src/cargo/ops/cargo_new.rs @@ -0,0 +1,94 @@ +use std::os; +use std::io; +use std::io::{fs, File, Command}; + +use ops; +use util::{CargoResult, human, ProcessError, Config, ChainError, process}; +use core::shell::MultiShell; +use core::source::Source; +use sources::PathSource; + +macro_rules! git( ($($a:expr),*) => ({ + process("git") $(.arg($a))* .exec_with_output() +}) ) + +pub struct NewOptions<'a> { + pub git: bool, + pub bin: bool, + pub path: &'a str, +} + +pub fn new(opts: NewOptions, shell: &mut MultiShell) -> CargoResult<()> { + let config = try!(Config::new(shell, false, None, None)); + let path = os::getcwd().join(opts.path); + if path.exists() { + return Err(human(format!("Destination `{}` already exists", + path.display()))) + } + let name = path.filename_str().unwrap(); + mk(&path, name, &opts).chain_error(|| { + human(format!("Failed to create project `{}` at `{}`", + name, path.display())) + }) +} + +fn mk(path: &Path, name: &str, opts: &NewOptions) -> CargoResult<()> { + + if opts.git { + try!(git!("init", path)); + try!(File::create(&path.join(".gitignore")).write(b"/target\n")); + } else { + try!(fs::mkdir(path, io::UserRWX)); + } + + let author = try!(discover_author()); + try!(File::create(&path.join("Cargo.toml")).write_str(format!( +r#"[package] + +name = "{}" +version = "0.0.1" +authors = ["{}"] +"#, name, author).as_slice())); + + try!(fs::mkdir(&path.join("src"), io::UserRWX)); + + if opts.bin { + try!(File::create(&path.join("src/main.rs")).write_str("\ +fn main() { + println!(\"Hello, world!\") +} +")); + } else { + try!(File::create(&path.join("src/lib.rs")).write_str("\ +#[test] +fn it_works() { +} +")); + } + + Ok(()) +} + +fn discover_author() -> CargoResult { + let name = match git!("config", "user.name") { + Ok(out) => String::from_utf8_lossy(out.output.as_slice()).into_string(), + Err(..) => match os::getenv("USER") { + Some(user) => user, + None => return Err(human("could not determine the current user, \ + please set $USER")) + } + }; + + let email = match git!("config", "user.email") { + Ok(out) => Some(String::from_utf8_lossy(out.output.as_slice()).into_string()), + Err(..) => None, + }; + + let name = name.as_slice().trim().to_string(); + let email = email.map(|s| s.as_slice().trim().to_string()); + + Ok(match (name, email) { + (name, Some(email)) => format!("{} <{}>", name, email), + (name, None) => name, + }) +} diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index cba0e894a..5c16029fe 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -3,9 +3,11 @@ pub use self::cargo_compile::{compile, CompileOptions}; pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages}; pub use self::cargo_rustc::compile_targets; pub use self::cargo_run::run; +pub use self::cargo_new::{new, NewOptions}; mod cargo_clean; mod cargo_compile; mod cargo_read_manifest; mod cargo_rustc; mod cargo_run; +mod cargo_new; diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index ef139607f..9b43be817 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -39,6 +39,8 @@ impl<'a> Config<'a> { }) } + pub fn home(&self) -> &Path { &self.home_path } + pub fn git_db_path(&self) -> Path { self.home_path.join(".cargo").join("git").join("db") } diff --git a/tests/test_cargo_compile_git_deps.rs b/tests/test_cargo_compile_git_deps.rs index 9af4b5004..3a0f52b2c 100644 --- a/tests/test_cargo_compile_git_deps.rs +++ b/tests/test_cargo_compile_git_deps.rs @@ -1,4 +1,4 @@ -use std::io::{File, TempDir}; +use std::io::File; use support::{ProjectBuilder, ResultTest, project, execs, main_file, paths}; use support::{cargo_dir}; diff --git a/tests/test_cargo_new.rs b/tests/test_cargo_new.rs new file mode 100644 index 000000000..0b1e87d8c --- /dev/null +++ b/tests/test_cargo_new.rs @@ -0,0 +1,103 @@ +use std::io::{fs, UserRWX, File}; +use std::os; + +use support::{execs, paths, cargo_dir, ResultTest}; +use hamcrest::{assert_that, existing_file, existing_dir}; + +use cargo::util::{process, ProcessBuilder}; + +fn setup() { +} + +fn my_process(s: &str) -> ProcessBuilder { + process(s) + .cwd(paths::root()) + .env("HOME", Some(paths::home())) +} + +fn cargo_process(s: &str) -> ProcessBuilder { + process(cargo_dir().join(s)) + .cwd(paths::root()) + .env("HOME", Some(paths::home())) +} + +test!(simple_lib { + os::setenv("USER", "foo"); + assert_that(cargo_process("cargo-new").arg("foo"), + execs().with_status(0)); + + assert_that(&paths::root().join("foo"), existing_dir()); + assert_that(&paths::root().join("foo/Cargo.toml"), existing_file()); + assert_that(&paths::root().join("foo/src/lib.rs"), existing_file()); + + assert_that(cargo_process("cargo-build").cwd(paths::root().join("foo")), + execs().with_status(0)); +}) + +test!(simple_bin { + os::setenv("USER", "foo"); + assert_that(cargo_process("cargo-new").arg("foo").arg("--bin"), + execs().with_status(0)); + + assert_that(&paths::root().join("foo"), existing_dir()); + assert_that(&paths::root().join("foo/Cargo.toml"), existing_file()); + assert_that(&paths::root().join("foo/src/main.rs"), existing_file()); + + assert_that(cargo_process("cargo-build").cwd(paths::root().join("foo")), + execs().with_status(0)); + assert_that(&paths::root().join(format!("foo/target/foo{}", + os::consts::EXE_SUFFIX)), + existing_file()); +}) + +test!(simple_git { + os::setenv("USER", "foo"); + assert_that(cargo_process("cargo-new").arg("foo").arg("--git"), + execs().with_status(0)); + + assert_that(&paths::root().join("foo"), existing_dir()); + assert_that(&paths::root().join("foo/Cargo.toml"), existing_file()); + assert_that(&paths::root().join("foo/src/lib.rs"), existing_file()); + assert_that(&paths::root().join("foo/.git"), existing_dir()); + assert_that(&paths::root().join("foo/.gitignore"), existing_file()); + + assert_that(cargo_process("cargo-build").cwd(paths::root().join("foo")), + execs().with_status(0)); +}) + +test!(no_argument { + assert_that(cargo_process("cargo-new"), + execs().with_status(1) + .with_stderr("must have a path as an argument\n")); +}) + +test!(existing { + let dst = paths::root().join("foo"); + fs::mkdir(&dst, UserRWX).assert(); + assert_that(cargo_process("cargo-new").arg("foo"), + execs().with_status(101) + .with_stderr(format!("Destination `{}` already exists\n", + dst.display()))); +}) + +test!(finds_author_user { + assert_that(cargo_process("cargo-new").arg("foo").env("USER", Some("foo")), + execs().with_status(0)); + + let toml = paths::root().join("foo/Cargo.toml"); + let toml = File::open(&toml).read_to_string().assert(); + assert!(toml.as_slice().contains(r#"authors = ["foo"]"#)); +}) + +test!(finds_author_git { + my_process("git").args(["config", "--global", "user.name", "bar"]) + .exec().assert(); + my_process("git").args(["config", "--global", "user.email", "baz"]) + .exec().assert(); + assert_that(cargo_process("cargo-new").arg("foo").env("USER", Some("foo")), + execs().with_status(0)); + + let toml = paths::root().join("foo/Cargo.toml"); + let toml = File::open(&toml).read_to_string().assert(); + assert!(toml.as_slice().contains(r#"authors = ["bar "]"#)); +}) diff --git a/tests/tests.rs b/tests/tests.rs index 0b1852708..80ca230b8 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -29,3 +29,4 @@ mod test_shell; mod test_cargo_cross_compile; mod test_cargo_run; mod test_cargo_version; +mod test_cargo_new; -- 2.30.2